package oop.ex2.expression;

import oop.ex2.exceptions.EmptyExpression;
import oop.ex2.exceptions.ExpectedAtomicExpression;
import oop.ex2.exceptions.ExpectedBinaryOperation;
import oop.ex2.exceptions.TerminatingStringNotFound;
import oop.ex2.exceptions.TokenNotRecognized;
import oop.ex2.exceptions.UnrecognizedVariable;
import oop.ex2.tokenizer.BinaryOpAnalyzer;
import oop.ex2.tokenizer.FunctionAnalyzer;
import oop.ex2.tokenizer.Tokenizer;
import oop.ex2.tokenizer.VariableAnalyzer;

/**
 * This class is in charge of parsing a given expression an calculating it's value.
 * It stores a tokenizer which it uses to parse the input and an expression tree which it uses
 * to evaluate the expression. In addition, it stores a variable table which it uses to evaluate
 * variables.
 * 
 * In order to return an evaluation, it first uses the tokenizer in order to create an expression
 * tree and then calls the expression tree's calculateTree function.
 * @author daniel_i_l
 *
 */
public class ExpressionSolver {
	private  ExpressionTree _expressionTree;
	private  Tokenizer _tokenizer;
	private  VariableTable _variableTable;
	
    /**
     * Construct an expression solver.
     */
	public ExpressionSolver ()
	{
		_expressionTree = new ExpressionTree();
		_tokenizer = new Tokenizer();
		_variableTable = new VariableTable();
		
		//add an analyzer that will construct nodes using the variable table
		_tokenizer.addAnalyzer(new VariableAnalyzer(_variableTable));
	}
	
	/**
	 * Evaluate a given expression.
	 * @param expression The expression to evaluate.
	 * @return The expression value.
	 * 
	 * @throws EmptyExpression an empty expression was encountered.
	 * @throws ExpectedAtomicExpression if an atomic symbol was expected and not found.
	 * @throws ExpectedBinaryOperation  if a binary operation was expected and not found.
	 * @throws TerminatingStringNotFound  if the end of an sequence was expected and not found.
	 * @throws TokenNotRecognized if an unrecognized token was encountered.
	 * @throws UnrecognizedVariable if an unrecognized variable was referenced.
	 */
	public double calculateExpression(String expression) 
	throws TokenNotRecognized, EmptyExpression, ExpectedAtomicExpression, 
	ExpectedBinaryOperation, TerminatingStringNotFound, UnrecognizedVariable
	{
		_tokenizer.setExpression(expression);
		_expressionTree.buildTree(_tokenizer);
		return _expressionTree.calculateTree();
	}
	
	/**
	 * Build an expression tree using the given expression.
	 * @param expression
	 * @return
	 * @throws EmptyExpression an empty expression was encountered.
	 * @throws TerminatingStringNotFound  if the end of an sequence was expected and not found.
	 * @throws TokenNotRecognized if an unrecognized token was encountered.
	 * @throws ExpectedAtomicExpression if an atomic symbol was expected and not found.
	 * @throws ExpectedBinaryOperation  if a binary operation was expected and not found.
	 */
	public ExpressionTree buildNewTree(String expression) throws TokenNotRecognized, EmptyExpression,
	                   ExpectedAtomicExpression, ExpectedBinaryOperation, TerminatingStringNotFound
	{
		_tokenizer.setExpression(expression);
		ExpressionTree expr = new ExpressionTree();
		expr.buildTree(_tokenizer);
		
		return expr;
	}
    
	/**
	 * Set a variable to a specified value
	 * @param varName The variable to set.
	 * @param value The value to set it to.
	 */
	public void setVarValue(String varName, double value)
	{
		_variableTable.setValue(varName, value);
	}
	
	/**
	 * Get the value of a variable.
	 * @param varName The variable to return.
	 * @return varName's value.
	 */
	public double getVarValue(String varName)
	{
		return _variableTable.getValue(varName);
	}
	
	/**
	 * Check if the variable has been defined.
	 * @param varName The variable to check.
	 * @return True if the variable has been defined and False otherwise.
	 */
	public boolean hasVariable(String varName)
	{
		return _variableTable.hasVariable(varName);
	}
	
	/**
	 * Add a function analyzer to the list of recognized functions.
	 * @param analyzer The function analyzer to add.
	 */
	public void addFunctionAnalyzer(FunctionAnalyzer analyzer)
	{
		_tokenizer.addFunctionAnalyzer(analyzer);
	}
	
	/**
	 * Remove a function analyzer from the list of recognized functions.
	 * @param functionName The function to remove.
	 */
	public void removeFunctionAnalyzer(String functionName)
	{
		_tokenizer.removeFunctionAnalyzer(functionName);
	}
	
	/**
	 * Get a function node corresponding to a specified function name.
	 * @param functionName The name of the specified function.
	 * @return A function node corresponding to the function functionName.
	 * @throws TokenNotRecognized If functionName isn't a recognized function. 
	 */
	public FunctionNode getFunctionNode(String functionName) throws TokenNotRecognized
	{
		return _tokenizer.getFunctionNode(functionName);
	}
	
	/**
	 * Add an operator to the list of recognized binary operators.
	 * @param analyzer The analyzer to add.
	 */
	public void addBinaryOpAnalyzer(BinaryOpAnalyzer analyzer)
	{
		_tokenizer.addBinaryOpAnalyzer(analyzer);
	}
	
	/**
	 * Remove an operator from the list of binary operators.
	 * @param op The operator to remove.
	 */
	public void removeBinaryOpAnalyzer(String op)
	{
		_tokenizer.removeBinaryOp(op);
	}

}
